/**
    \file escape.c
    \brief Scan the abstract tree and mark the escape variable
    
    As the semant routine need to know if a variable is escape at the time
    semant routine neet is, Esc_findEscape functinon must be called before
    semant.
*/

#include "escape.h"
#include "types.h"
#include <assert.h>
#include "table.h"

typedef struct Esc_entry_ *Esc_entry;

///Traverse the A_exp to search for the escape variable
static void traverseExp(S_table env, int depth, A_exp e);
///Traverse the A_dev to search for the escape variable
static void traverseDec(S_table env, int depth, A_dec d);
///Traverse the A_var to search for the escape variable
static void traverseVar(S_table env, int depth, A_var v);

/**    Get the escape environment
    @return A S_table environment which contains all the escape variables in 
    current scope
*/
S_table Esc_env();

///Entry structure of the escape variable for the escape environment
struct Esc_entry_
{
    ///The type of the entry
    enum { Esc_dec, Esc_field} kind;
    ///The scope depth of the variable
    int     depth;
    ///Is it escape?
    bool     escape;
    union
    {
        A_dec     dec;
        A_field field;
    }u;
};

/**Create a new escape entry
@param depth The scope depth of the variable
@param dec The dec variable in the abstract tree
@param escape Is is escape?
@return A new escape entry for escape environment
*/
Esc_entry Esc_EntryDec(int depth, A_dec dec, bool escape)
{
    Esc_entry p = checked_malloc(sizeof(*p));
    p->kind = Esc_dec;
    p->depth = depth;
    p->u.dec = dec;
    p->escape = escape;
    return p;
}

Esc_entry Esc_EntryField(int depth, A_field field, bool escape)
{
    Esc_entry p = checked_malloc(sizeof(*p));
    p->kind = Esc_field;
    p->depth = depth;
    p->u.field = field;
    p->escape = escape;
    return p;
}

void Esc_enter(S_table env, S_symbol name, Esc_entry escape)
{
    TAB_enter(env, name, escape);
};



S_table Esc_env()
{
    return S_empty();
};

void Esc_findEscape(A_exp exp)
{
    S_table env = Esc_env();
    traverseExp(env, 0, exp);
}

static void traverseExp(S_table env, int depth, A_exp e)
{
    if (!e)
        return;

    switch(e->kind)
    {
    case A_varExp:
        traverseVar(env, depth, e->u.var);
        return;
    case A_opExp:
            traverseExp(env, depth, e->u.op.left);
            traverseExp(env, depth, e->u.op.right);
            return;
    case A_recordExp:
        return;
    case A_assignExp:
            traverseVar(env, depth, e->u.assign.var);
            traverseExp(env, depth, e->u.assign.exp);
            return;
    case A_ifExp:
        traverseExp(env, depth, e->u.iff.test);
        traverseExp(env, depth, e->u.iff.then);
        traverseExp(env, depth, e->u.iff.elsee);
        return;
    case A_whileExp:
        traverseExp(env, depth, e->u.whilee.test);
        traverseExp(env, depth, e->u.whilee.body);
        return;
    case A_forExp:
        traverseExp(env, depth, e->u.forr.test);
        traverseExp(env, depth, e->u.forr.lo);
        traverseExp(env, depth, e->u.forr.hi);
        traverseExp(env, depth, e->u.forr.body);
            return;
    case A_seqExp:
    {
        A_expList lptr = e->u.seq;
        do
        {
            traverseExp(env, depth, lptr->data);
            lptr = lptr->next;
        } while (lptr);
        return;
    }
    case A_decExp:
        traverseDec(env, depth, e->u.dec);
        return;
    case A_callExp:
    {
        A_expList lptr = e->u.call.args;
        while(lptr)
        {
            traverseExp(env, depth, lptr->data);
            lptr = lptr->next;
        }
        return;

    }
    case A_stringExp:
    case A_intExp:
    case A_asmExp:
    case A_breakExp:
        return;
    }
    assert(0);
}

static void traverseDec(S_table env, int depth, A_dec d)
{
    switch(d->kind)
    {
    case A_funDec:
        S_beginScope(env);
        {
            A_fieldList l;
            for (l = d->u.fun.params; l; l = l->next)
                Esc_enter(env, l->data->name,
                    Esc_EntryField(depth+1, l->data, FALSE));
        }
        traverseExp(env, depth+1, d->u.fun.body);
        S_endScope(env);
        return;
    case A_typeDec:
        return;
    case A_varDec:
        Esc_enter(env, d->u.var.var,
            Esc_EntryDec(depth, d, FALSE));
        return;
    }
    assert(0);
}

static void traverseVar(S_table env, int depth, A_var v)
{
    if (v->kind == A_simpleVar)
    {
        Esc_entry e = S_look(env, v->u.simple);
        if (e && e->depth < depth)
        {
            if (e->kind == Esc_dec)
                e->u.dec->u.var.escape = TRUE;
            else
                e->u.field->escape = TRUE;
            e->escape = TRUE;
            return;
        }
        return;
    }
    //assert(0);
}
